﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using Biosek;

namespace DestinoSegmentador.Models
{
	/// <summary>
	/// NFA (Non-Deterministic Finite Automata) model loader exported using FastOIL (https://code.google.com/p/fast-oil/)
	/// </summary>
	[Export(typeof(IPatternMatchingModelLoader))]
	public class NFA_FastOIL_Loader : IPatternMatchingModelLoader
	{
		#region Miembros de IPatternMatchingModelLoader

		enum parsing_state { header_alphabet, header_states, header_initial, header_final, header_transitions_count, body_transitions };

		/// <summary>
		/// Load NFA model from stream
		/// </summary>
		/// <param name="stream"></param>
		/// <returns></returns>
		public IPatternMatchingModel LoadModel(Stream stream)
		{
			var file = new StreamReader(stream);
			var state = parsing_state.header_alphabet;

			int alpha = 0;
			int states = 0;
			BitArray initial = null;
			BitArray final = null;
			BitArray[,] forward = null;
			int transitionsCount = 0;
			int currentTransition = 0;
			_OilNFA nfa = null;

			while (!file.EndOfStream)
			{
				var line = file.ReadLine().Trim();

				if (line.StartsWith("#")) continue;
				if (state == parsing_state.header_alphabet)
				{
					alpha = int.Parse(line, CultureInfo.InvariantCulture);
					state = parsing_state.header_states;
				}
				else if (state == parsing_state.header_states)
				{
					states = int.Parse(line, CultureInfo.InvariantCulture);
					state = parsing_state.header_initial;
				}
				else if (state == parsing_state.header_initial)
				{
					var splits = line.Split(' ');
					var ini = splits.Select(x => int.Parse(x, CultureInfo.InvariantCulture));
					initial = new BitArray(states);
					foreach (var item in ini)
					{
						initial.Set(item, true);
					}
					state = parsing_state.header_final;
				}
				else if (state == parsing_state.header_final)
				{
					var splits = line.Split(' ');
					var fin = splits.Select(x => int.Parse(x, CultureInfo.InvariantCulture));
					final = new BitArray(states);
					foreach (var item in fin)
					{
						final.Set(item, true);
					}
					state = parsing_state.header_transitions_count;
				}
				else if (state == parsing_state.header_transitions_count)
				{
					transitionsCount = int.Parse(line, CultureInfo.InvariantCulture);
					currentTransition = 0;
					forward = new BitArray[states, alpha];
					state = parsing_state.body_transitions;
				}
				else if (state == parsing_state.body_transitions)
				{
					var splits = line.Split(' ');
					var src = int.Parse(splits[0], CultureInfo.InvariantCulture);
					var dst = int.Parse(splits[1], CultureInfo.InvariantCulture);
					var sym = int.Parse(splits[2], CultureInfo.InvariantCulture);
					if (src >= states)
					{
						throw new InvalidOperationException("Invalid source state number");
					}
					if (dst >= states)
					{
						throw new InvalidOperationException("Invalid destination state number");
					}
					if (sym >= alpha)
					{
						throw new InvalidOperationException("Invalid symbol");
					}
					if (currentTransition >= transitionsCount)
					{
						throw new InvalidOperationException("Invalid transitions count");
					}
					if (forward[src, sym] == null) forward[src, sym] = new BitArray(states);
					forward[src, sym].Set(dst, true);
					currentTransition++;
				}
			}
			if (state == parsing_state.body_transitions)
			{
				nfa = new _OilNFA(initial, final, forward);
				return nfa;
			}
			throw new InvalidOperationException();
		}

		#endregion
	}

	/// <summary>
	/// Extension methods for BitArray
	/// </summary>
	public static class BitArrayExtensions
	{
		public static bool Any(this BitArray array)
		{
			foreach (bool item in array)
			{
				if (item) return true;
			}
			return false;
		}
	}

	/// <summary>
	/// NFA model
	/// </summary>
	public class OilNFA
	{
		BitArray[,] forwardMatrix;
		BitArray initialStates;
		BitArray finalStates;

		/// <summary>
		/// Constructor for NFA model
		/// </summary>
		/// <param name="initial">Initial states. One bit per state</param>
		/// <param name="final">Final states. One bit per state</param>
		/// <param name="forward">Transitions per state and symbol</param>
		public OilNFA(BitArray initial, BitArray final, BitArray[,] forward)
		{
			initialStates = initial;
			finalStates = final;
			forwardMatrix = forward;
		}

		/// <summary>
		/// Evaluate the matching using the NFA
		/// </summary>
		/// <param name="sample">sequence of symbols</param>
		/// <returns>Acceptance value</returns>
		public bool IsMatch(IEnumerable<int> sample)
		{
			BitArray current = (BitArray)initialStates.Clone();
			BitArray next = new BitArray(initialStates.Length);

			foreach (var sym in sample)
			{
				next.SetAll(false);
				for (var i = 0; i < initialStates.Length; i++)
				{
					var isNext = current.Get(i);
					if (!isNext) continue;
					var fwd = forwardMatrix[i, sym];
					next.Or(fwd);
				}
				if (!next.Any())
				{
					return false;
				}
				var tmp = next; // swap
				next = current;
				current = tmp;
			}
			var rt = current.And(finalStates);
			var match = rt.Any();
			return match;
		}
	}

	/// <summary>
	/// NFA model implementing <typeparamref name="IPatterMatchingModel"/>
	/// </summary>
	public class _OilNFA : OilNFA, IPatternMatchingModel
	{
		public _OilNFA(BitArray initial, BitArray final, BitArray[,] forward) :
			base(initial, final, forward)
		{
		}

		#region Miembros de IPatternMatchingModel

		/// <summary>
		/// Accepted sample data types
		/// </summary>
		public IEnumerable<Type> AcceptedSampleTypes
		{
			get { yield return typeof(IEnumerable<int>); }
		}

		/// <summary>
		/// Evaluate matching as real value
		/// <remarks>NOT IMPLEMENTED FOR NFA</remarks>
		/// </summary>
		/// <param name="samples">Sample set</param>
		/// <returns>Matching value for each sample</returns>
		public IEnumerable<float> EvaluateMatch(IEnumerable samples)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// Evaluate matching as binary value
		/// </summary>
		/// <param name="samples">Sample set</param>
		/// <returns>Matching value for each sample</returns>
		public IEnumerable<bool> EvaluateMatchBinary(IEnumerable samples)
		{
			foreach (var item in samples)
			{
				yield return IsMatch(item as IEnumerable<int>);
			}
		}

		/// <summary>
		/// Because NFA outputs an acceptance boolean value, its output data type is binary only.
		/// </summary>
		public bool IsBinaryResponse
		{
			get { return true; }
		}

		#endregion
	}
}
